/*
 * Copyright 2010-2011 CGT&DC ISA RAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package websolve.workflow.wfms;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import org.codehaus.jettison.json.JSONObject;

import websolve.everest.Input;
import websolve.everest.Service;
import websolve.everest.ServiceDescription;
import websolve.everest.ServiceManager;
import websolve.everest.adapter.Adapter;
import websolve.everest.adapter.java.JavaAdapterFactory;
import websolve.everest.security.AccessPolicy;
import websolve.workflow.runtime.Workflow;
import websolve.workflow.util.ErrorUtils;
import websolve.workflow.util.FileUtils;

public class WorkflowManager {

    private String wfDir;
    private ServiceManager serviceMan;
    private String baseURI;

    private Map<String, WorkflowEntry> workflows = new HashMap<String, WorkflowEntry>();
    
    private JavaAdapterFactory adapterFactory;

    public WorkflowManager(String wfDir, ServiceManager serviceMan, String baseURI) {
        this.wfDir = wfDir;
        this.serviceMan = serviceMan;
        this.baseURI = baseURI;
        adapterFactory = new JavaAdapterFactory();
        for (File file : new File(wfDir).listFiles()) {
            if (file.isFile()) {
                String workflowId = file.getName();
                try {
                    JSONObject desc = new JSONObject(new String(FileUtils.readFile(file.getAbsolutePath())));
                    String owner = null;
                    if (desc.has("owner")) {
                        owner = desc.getString("owner");
                    }

                    workflows.put(workflowId, new WorkflowEntry(desc, owner));
                    log.info("Loaded workflow " + workflowId + " from " + file.getAbsolutePath());

                    deployWorkflowService(workflowId, desc);

                } catch (Exception e) {
                    log.severe("Failed to load workflow " + workflowId + ": " + ErrorUtils.stackTraceToString(e));
                }
            }
        }
    }

    public String addWorkflow(String description, String owner) throws Exception {
        String workflowId = UUID.randomUUID().toString();
        log.fine("Adding workflow: " + workflowId);
        log.finer("Workflow " + workflowId + " description: " + description);

        JSONObject json = new JSONObject(description);
        if (owner != null) {
            json.put("owner", owner);
        }
        WorkflowEntry wf = new WorkflowEntry(json, owner);

        FileUtils.writeFile(wfDir + "/" + workflowId, json.toString().getBytes());
        log.fine("Saved workflow " + workflowId + " in file: " + (new File(wfDir + "/" + workflowId)).getAbsolutePath());

        workflows.put(workflowId, wf);

        deployWorkflowService(workflowId, json);

        return workflowId;
    }

    public boolean hasWorkflow(String workflowId) {
        return workflows.containsKey(workflowId);
    }

    public WorkflowEntry getWorkflow(String workflowId) {
        log.fine("Get workflow: " + workflowId);
        if (hasWorkflow(workflowId)) {
            return workflows.get(workflowId);
        } else {
            return null;
        }
    }

    public Map<String, WorkflowEntry> getWorkflows() {
        log.fine("Get workflows");
        return Collections.unmodifiableMap(workflows);
    }

    public void updateWorkflow(String workflowId, String description) throws Exception {
        log.fine("Updating workflow: " + workflowId);
        log.finer("Workflow " + workflowId + " description: " + description);

        WorkflowEntry wf = workflows.get(workflowId);
        JSONObject json = new JSONObject(description);
        if (wf.getOwner() != null) {
            json.put("owner", wf.getOwner());
        }

        FileUtils.writeFile(wfDir + "/" + workflowId, json.toString().getBytes());
        log.fine("Saved workflow " + workflowId + " in file: " + (new File(wfDir + "/" + workflowId)).getAbsolutePath());

        wf.setDescription(json);

        deployWorkflowService(workflowId, json);
    }

    public void removeWorkflow(String workflowId) {
        log.fine("Removing workflow: " + workflowId);
        serviceMan.removeService(workflowId);
        log.fine("Undeployed workflow " + workflowId + " in EveREST");
        workflows.remove(workflowId);
        new File(wfDir + "/" + workflowId).delete();
        log.fine("Deleted workflow " + workflowId + " file");
    }

    public void addInstance(String workflowId, String instanceId, Workflow wf) {
        workflows.get(workflowId).addInstance(instanceId, wf);
        log.fine("Added workflow instance: " + workflowId + "/" + instanceId);
    }

    public void runInstance(String workflowId, String instanceId) throws Exception {
        Workflow wf = getInstance(workflowId, instanceId);
        Thread t = new Thread(wf);
        t.start();
        log.fine("Started workflow instance: " + workflowId + "/" + instanceId);
    }

    public Workflow getInstance(String workflowId, String instanceId) {
        log.fine("Get workflow instance: " + workflowId + "/" + instanceId);
        return workflows.get(workflowId).getInstance(instanceId);
    }

    public void removeInstance(String workflowId, String instanceId) {
        workflows.get(workflowId).removeInstance(instanceId);
        log.fine("Removed workflow instance: " + workflowId + "/" + instanceId);
    }

    private void deployWorkflowService(String workflowId, JSONObject desc) throws Exception {
        ServiceDescription serviceDesc = JSONParser.parseService(workflowId, desc, this);
        WorkflowService servant = new WorkflowService(workflowId, desc, this, baseURI + "/services/" + workflowId);
        List<String> remoteInputs = new ArrayList<String>();
        for (Input input : serviceDesc.getInParams()) {
            if (input.getType().equals("file")) {
                remoteInputs.add(input.getName());
            }
        }
        Adapter adapter = adapterFactory.create(workflowId, serviceDesc, servant, remoteInputs);
        
        // TODO - workflow is accessible to any authorized user
        AccessPolicy accessPolicy = new AccessPolicy(new ArrayList<String>(), null);
        
        serviceMan.addService(new Service(workflowId, serviceDesc, adapter, accessPolicy));
        log.fine("Redeployed workflow " + workflowId + " as a service in EveREST");
    }

    public static WorkflowManager getInstance(ServletContext ctx) {
        return (WorkflowManager) (ctx.getAttribute("workflow-manager"));
    }

    private static final Logger log = Logger.getLogger(WorkflowManager.class.getName());
}
